// 版权所有2011 Go作者。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package httptest

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
	"net/textproto"
	"strconv"
	"strings"

	"golang.org/x/net/http/httpguts"
)

// ResponseRecorder是http.ResponseWriter的一个实现，
// 记录其突变，以备以后在测试中检查。
type ResponseRecorder struct {
	// 代码是WriteHeader设置的HTTP响应代码。
	// 
	// 请注意，如果处理程序从未调用WriteHeader或Write，
	// 这可能最终为0，而不是隐式的
	// http.StatusOK。要获取隐式值，请使用结果
	// 方法。
	Code int

	// HeaderMap包含处理程序显式设置的头。
	// 这是一个内部细节。
	// 
	// 已弃用：存在HeaderMap是为了历史兼容性
	// 不应使用。要访问处理程序返回的头，
	// 使用Result方法返回的Response.Header映射。
	HeaderMap http.Header

	// Body是处理程序的写调用发送到的缓冲区。
	// 如果为零，则写入操作将被静默丢弃。
	Body *bytes.Buffer

	// Flush是指处理程序是否调用Flush。
	Flushed bool

	result      *http.Response // 结果返回值的缓存
	snapHeader  http.Header    // 头映射首次写入时的快照
	wroteHeader bool
}

// NewRecorder返回初始化的响应记录器。
func NewRecorder() *ResponseRecorder {
	return &ResponseRecorder{
		HeaderMap: make(http.Header),
		Body:      new(bytes.Buffer),
		Code:      200,
	}
}

// DefaultRemoteAddr是在响应记录器上未设置显式DefaultRemoteAddr时，在RemoteAddr中返回的默认远程地址。
const DefaultRemoteAddr = "1.2.3.4"

// 头实现http.ResponseWriter。它返回响应
// 头以在处理程序中进行变异。要测试处理程序完成后写入的头，请使用Result方法并查看返回的响应值的头。
func (rw *ResponseRecorder) Header() http.Header {
	m := rw.HeaderMap
	if m == nil {
		m = make(http.Header)
		rw.HeaderMap = m
	}
	return m
}

// writeHeader在未写入头时写入头，
// 在需要时检测内容类型。
// 
// 字节或str是响应体的开头。
// 我们传递这两个参数是为了避免在rw.WriteString中不必要地生成垃圾
// 这是出于性能原因而创建的。
// 非零字节赢。
func (rw *ResponseRecorder) writeHeader(b []byte, str string) {
	if rw.wroteHeader {
		return
	}
	if len(str) > 512 {
		str = str[:512]
	}

	m := rw.Header()

	_, hasType := m["Content-Type"]
	hasTE := m.Get("Transfer-Encoding") != ""
	if !hasType && !hasTE {
		if b == nil {
			b = []byte(str)
		}
		m.Set("Content-Type", http.DetectContentType(b))
	}

	rw.WriteHeader(200)
}

// Write实现http.ResponseWriter。buf中的数据写入
// rw.Body，如果不是空的话。
func (rw *ResponseRecorder) Write(buf []byte) (int, error) {
	rw.writeHeader(buf, "")
	if rw.Body != nil {
		rw.Body.Write(buf)
	}
	return len(buf), nil
}

// WriteString实现io.StringWriter。str中的数据被写入rw.Body，如果不是nil。
func (rw *ResponseRecorder) WriteString(str string) (int, error) {
	rw.writeHeader(nil, str)
	if rw.Body != nil {
		rw.Body.WriteString(str)
	}
	return len(str), nil
}

func checkWriteHeaderCode(code int) {
	// 问题22880:需要有效的写入头状态代码。
	// 现在我们只强制要求它是三位数。
	// 将来我们可能会阻止超过599的内容（600及以上没有定义
	// 在https:
	// 并且我们可能阻止在200以下（一旦我们有了更成熟的1x支持）。
	// 但是现在任何三位数。
	// 
	// 我们以前发送“HTTP/1.1000 0”在回应的连线中，有
	// 我们无法在HTTP/2中发送同等的虚假信息，
	// 因此我们将始终保持恐慌，帮助人们尽早找到他们的错误
	// 即使我们想返回，我们也无法从WriteHeader返回错误。）
	if code < 100 || code > 999 {
		panic(fmt.Sprintf("invalid WriteHeader code %v", code))
	}
}

// WriteHeader实现http.ResponseWriter.
func (rw *ResponseRecorder) WriteHeader(code int) {
	if rw.wroteHeader {
		return
	}

	checkWriteHeaderCode(code)
	rw.Code = code
	rw.wroteHeader = true
	if rw.HeaderMap == nil {
		rw.HeaderMap = make(http.Header)
	}
	rw.snapHeader = rw.HeaderMap.Clone()
}

// Flush实现http.Flusher。若要测试Flush是否被调用，请参阅rw.Flush.
func (rw *ResponseRecorder) Flush() {
	if !rw.wroteHeader {
		rw.WriteHeader(200)
	}
	rw.Flushed = true
}

// Result返回由处理程序生成的响应。
// 
// 返回的响应至少有其StatusCode，
// 标头、正文和可选的尾部已填充。
// 将来可能会填充更多字段，因此调用方应
// 在测试中不等于结果。
// 
// 响应。标头是
// 第一个wr时标头的快照如果处理程序从未执行过write.
// 
// 响应。Body保证为非nil且Body。Read调用为
// 保证不会返回除io.EOF.
// 
// 结果只能在t之后调用处理程序已完成运行。
func (rw *ResponseRecorder) Result() *http.Response {
	if rw.result != nil {
		return rw.result
	}
	if rw.snapHeader == nil {
		rw.snapHeader = rw.HeaderMap.Clone()
	}
	res := &http.Response{
		Proto:      "HTTP/1.1",
		ProtoMajor: 1,
		ProtoMinor: 1,
		StatusCode: rw.Code,
		Header:     rw.snapHeader,
	}
	rw.result = res
	if res.StatusCode == 0 {
		res.StatusCode = 200
	}
	res.Status = fmt.Sprintf("%03d %s", res.StatusCode, http.StatusText(res.StatusCode))
	if rw.Body != nil {
		res.Body = io.NopCloser(bytes.NewReader(rw.Body.Bytes()))
	} else {
		res.Body = http.NoBody
	}
	res.ContentLength = parseContentLength(res.Header.Get("Content-Length"))

	if trailers, ok := rw.snapHeader["Trailer"]; ok {
		res.Trailer = make(http.Header, len(trailers))
		for _, k := range trailers {
			k = http.CanonicalHeaderKey(k)
			if !httpguts.ValidTrailerHeader(k) {
				// Ignore因为RFC 7230第4.1.2节禁止。
				continue
			}
			vv, ok := rw.HeaderMap[k]
			if !ok {
				continue
			}
			vv2 := make([]string, len(vv))
			copy(vv2, vv)
			res.Trailer[k] = vv2
		}
	}
	for k, vv := range rw.HeaderMap {
		if !strings.HasPrefix(k, http.TrailerPrefix) {
			continue
		}
		if res.Trailer == nil {
			res.Trailer = make(http.Header)
		}
		for _, v := range vv {
			res.Trailer.Add(strings.TrimPrefix(k, http.TrailerPrefix), v)
		}
	}
	return res
}

// parseContentLength从s中删除空格，如果未设置值
// 则返回-1，如果值>=0，则返回值。
// 
// 这是net/http中相同函数的修改版本/transfer.go.This 
// 只会忽略一个无效的头。
func parseContentLength(cl string) int64 {
	cl = textproto.TrimString(cl)
	if cl == "" {
		return -1
	}
	n, err := strconv.ParseUint(cl, 10, 63)
	if err != nil {
		return -1
	}
	return int64(n)
}
